﻿using Sidvall.Serialization;
using System.Collections.Generic;

namespace Sidvall.UI.Entities
{
    public sealed class PageItem : BusinessBase
    {
        #region Public Members

        #region TotalRowCount

        private long _TotalRowCount;
        public long TotalRowCount
        {
            get
            {
                return _TotalRowCount;
            }
            set
            {
                if (_TotalRowCount == value)
                    return;
                _TotalRowCount = value;
                OnPropertyChanged(nameof(TotalRowCount), true, true);
                OnPropertyChanged(nameof(PageCount), true, false);
                UpdateCurrentPage(this.CurrentPage, true);
            }
        }

        #endregion
        #region PageSize

        private int _PageSize = 100;
        public int PageSize
        {
            get
            {
                return _PageSize;
            }
            set
            {
                if (_PageSize == value)
                    return;
                _PageSize = value;
                OnPropertyChanged(nameof(PageSize), true, true);
                OnPropertyChanged(nameof(PageCount), true, false);
                UpdateCurrentPage(this.CurrentPage, true);
            }
        }

        #endregion
        #region CurrentPage

        private int _CurrentPage = 1;
        public int CurrentPage
        {
            get
            {
                return _CurrentPage;
            }
            set
            {
                UpdateCurrentPage(value, false);
            }
        }

        #endregion
        #region PageItemStartCount

        private int _PageItemStartCount = 0;
        public int PageItemStartCount
        {
            get
            {
                return _PageItemStartCount;
            }
            set
            {
                value = GetValidPageCount(value);
                if (_PageItemStartCount != value)
                {
                    _PageItemStartCount = value;
                    OnPropertyChanged(nameof(PageItemStartCount), true, true);
                    OnUpdatePageItemCount();
                }
            }
        }

        #endregion
        #region PageItemPreviousCount

        private int _PageItemPreviousCount = 0;
        public int PageItemPreviousCount
        {
            get
            {
                return _PageItemPreviousCount;
            }
            set
            {
                value = GetValidPageCount(value);
                if (_PageItemPreviousCount != value)
                {
                    _PageItemPreviousCount = value;
                    OnPropertyChanged(nameof(PageItemPreviousCount), true, true);
                    OnUpdatePageItemCount();
                }
            }
        }

        #endregion
        #region PageItemNextCount

        private int _PageItemNextCount = 0;
        public int PageItemNextCount
        {
            get
            {
                return _PageItemNextCount;
            }
            set
            {
                value = GetValidPageCount(value);
                if (_PageItemNextCount != value)
                {
                    _PageItemNextCount = value;
                    OnPropertyChanged(nameof(PageItemNextCount), true, true);
                    OnUpdatePageItemCount();
                }
            }
        }

        #endregion
        #region PageItemEndCount

        private int _PageItemEndCount = 0;
        public int PageItemEndCount
        {
            get
            {
                return _PageItemEndCount;
            }
            set
            {
                value = GetValidPageCount(value);
                if (_PageItemEndCount != value)
                {
                    _PageItemEndCount = value;
                    OnPropertyChanged(nameof(PageItemEndCount), true, true);
                    OnUpdatePageItemCount();
                }
            }
        }

        #endregion
        #region DescriptionFormat

        private string _DescriptionFormat;
        public string DescriptionFormat
        {
            get
            {
                return _DescriptionFormat;
            }
            set
            {
                if (_DescriptionFormat == value)
                    return;
                _DescriptionFormat = value;
                OnPropertyChanged(nameof(DescriptionFormat), true, true);
            }
        }

        #endregion

        // Read-only
        #region CurrentStartPosition

        public long CurrentStartPosition
        {
            get
            {
                if (this.CurrentPage < 1)
                    return 0;
                return (this.CurrentPage - 1) * this.PageSize + 1;
            }
        }

        #endregion
        #region CurrentEndPosition

        public long CurrentEndPosition
        {
            get
            {
                if (this.CurrentStartPosition < 1)
                    return 0;
                if (this.CurrentPage == this.PageCount)
                    return this.TotalRowCount;
                return this.CurrentStartPosition + this.PageSize - 1;
            }
        }

        #endregion
        #region PageCount

        public int PageCount
        {
            get
            {
                if (this.TotalRowCount % this.PageSize > 0)
                    return (int)(this.TotalRowCount / this.PageSize + 1);
                return (int)(this.TotalRowCount / this.PageSize);
            }
        }

        #endregion
        #region CanPageBack

        public bool CanPageBack
        {
            get
            {
                return this.CurrentPage > 1;
            }
        }

        #endregion
        #region CanPageForward

        public bool CanPageForward
        {
            get
            {
                return this.CurrentPage < this.PageCount;
            }
        }

        #endregion
        #region Description

        public string Description
        {
            get
            {
                if (!string.IsNullOrWhiteSpace(this.DescriptionFormat))
                {
                    return this.DescriptionFormat.Replace("{TotalRowCount}",
                        this.TotalRowCount.ToString(System.Globalization.CultureInfo.InvariantCulture)).Replace("{PageSize}",
                        this.PageSize.ToString(System.Globalization.CultureInfo.InvariantCulture)).Replace("{CurrentPage}",
                        this.CurrentPage.ToString(System.Globalization.CultureInfo.InvariantCulture)).Replace("{CurrentStartPosition}",
                        this.CurrentStartPosition.ToString(System.Globalization.CultureInfo.InvariantCulture)).Replace("{CurrentEndPosition}",
                        this.CurrentEndPosition.ToString(System.Globalization.CultureInfo.InvariantCulture)).Replace("{PageCount}",
                        this.PageCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
                }
                return this.CurrentStartPosition.ToString(System.Globalization.CultureInfo.InvariantCulture)
                    + "-" + this.CurrentEndPosition.ToString(System.Globalization.CultureInfo.InvariantCulture)
                    + " (of " + this.TotalRowCount.ToString(System.Globalization.CultureInfo.InvariantCulture) + ")";
            }
        }

        #endregion
        #region HasPageItemCurrentPage

        public bool HasPageItemCurrentPage
        {
            get
            {
                return new List<int?>()
                {
                    this.PageItemStart1,
                    this.PageItemStart2,
                    this.PageItemStart3,
                    this.PageItemStart4,
                    this.PageItemPrevious1,
                    this.PageItemPrevious2,
                    this.PageItemPrevious3,
                    this.PageItemPrevious4,
                    this.PageItemNext1,
                    this.PageItemNext2,
                    this.PageItemNext3,
                    this.PageItemNext4,
                    this.PageItemEnd1,
                    this.PageItemEnd2,
                    this.PageItemEnd3,
                    this.PageItemEnd4,
                }.Contains(this.CurrentPage);
            }
        }

        #endregion
        #region HasPageItemStartGap

        public bool HasPageItemStartGap
        {
            get
            {
                var start = MaxStart;
                if (start != null)
                {
                    var end = MinAllExceptStart;
                    if (end != null)
                    {
                        if (start < end - 1)
                            return true;
                    }
                }
                return false;
            }
        }

        #endregion
        #region HasPageItemEndGap

        public bool HasPageItemEndGap
        {
            get
            {
                var start = MaxAllExceptStartAndEnd;
                if (start != null)
                {
                    var end = MinEnd;
                    if (end != null)
                    {
                        if (start < end - 1)
                            return true;
                    }
                }
                return false;
            }
        }

        #endregion
        #region PageItemStart1

        private int? _PageItemStart1;
        public int? PageItemStart1
        {
            get
            {
                return _PageItemStart1;
            }
            private set
            {
                if (_PageItemStart1 == value)
                    return;
                _PageItemStart1 = value;
                OnPropertyChanged(nameof(PageItemStart1), true, false);
            }
        }

        #endregion
        #region PageItemStart2

        private int? _PageItemStart2;
        public int? PageItemStart2
        {
            get
            {
                return _PageItemStart2;
            }
            private set
            {
                if (_PageItemStart2 == value)
                    return;
                _PageItemStart2 = value;
                OnPropertyChanged(nameof(PageItemStart2), true, false);
            }
        }

        #endregion
        #region PageItemStart3

        private int? _PageItemStart3;
        public int? PageItemStart3
        {
            get
            {
                return _PageItemStart3;
            }
            private set
            {
                if (_PageItemStart3 == value)
                    return;
                _PageItemStart3 = value;
                OnPropertyChanged(nameof(PageItemStart3), true, false);
            }
        }

        #endregion
        #region PageItemStart4

        private int? _PageItemStart4;
        public int? PageItemStart4
        {
            get
            {
                return _PageItemStart4;
            }
            private set
            {
                if (_PageItemStart4 == value)
                    return;
                _PageItemStart4 = value;
                OnPropertyChanged(nameof(PageItemStart4), true, false);
            }
        }

        #endregion
        #region PageItemPrevious1

        private int? _PageItemPrevious1;
        public int? PageItemPrevious1
        {
            get
            {
                return _PageItemPrevious1;
            }
            private set
            {
                if (_PageItemPrevious1 == value)
                    return;
                _PageItemPrevious1 = value;
                OnPropertyChanged(nameof(PageItemPrevious1), true, false);
            }
        }

        #endregion
        #region PageItemPrevious2

        private int? _PageItemPrevious2;
        public int? PageItemPrevious2
        {
            get
            {
                return _PageItemPrevious2;
            }
            private set
            {
                if (_PageItemPrevious2 == value)
                    return;
                _PageItemPrevious2 = value;
                OnPropertyChanged(nameof(PageItemPrevious2), true, false);
            }
        }

        #endregion
        #region PageItemPrevious3

        private int? _PageItemPrevious3;
        public int? PageItemPrevious3
        {
            get
            {
                return _PageItemPrevious3;
            }
            private set
            {
                if (_PageItemPrevious3 == value)
                    return;
                _PageItemPrevious3 = value;
                OnPropertyChanged(nameof(PageItemPrevious3), true, false);
            }
        }

        #endregion
        #region PageItemPrevious4

        private int? _PageItemPrevious4;
        public int? PageItemPrevious4
        {
            get
            {
                return _PageItemPrevious4;
            }
            private set
            {
                if (_PageItemPrevious4 == value)
                    return;
                _PageItemPrevious4 = value;
                OnPropertyChanged(nameof(PageItemPrevious4), true, false);
            }
        }

        #endregion
        #region PageItemNext1

        private int? _PageItemNext1;
        public int? PageItemNext1
        {
            get
            {
                return _PageItemNext1;
            }
            private set
            {
                if (_PageItemNext1 == value)
                    return;
                _PageItemNext1 = value;
                OnPropertyChanged(nameof(PageItemNext1), true, false);
            }
        }

        #endregion
        #region PageItemNext2

        private int? _PageItemNext2;
        public int? PageItemNext2
        {
            get
            {
                return _PageItemNext2;
            }
            private set
            {
                if (_PageItemNext2 == value)
                    return;
                _PageItemNext2 = value;
                OnPropertyChanged(nameof(PageItemNext2), true, false);
            }
        }

        #endregion
        #region PageItemNext3

        private int? _PageItemNext3;
        public int? PageItemNext3
        {
            get
            {
                return _PageItemNext3;
            }
            private set
            {
                if (_PageItemNext3 == value)
                    return;
                _PageItemNext3 = value;
                OnPropertyChanged(nameof(PageItemNext3), true, false);
            }
        }

        #endregion
        #region PageItemNext4

        private int? _PageItemNext4;
        public int? PageItemNext4
        {
            get
            {
                return _PageItemNext4;
            }
            private set
            {
                if (_PageItemNext4 == value)
                    return;
                _PageItemNext4 = value;
                OnPropertyChanged(nameof(PageItemNext4), true, false);
            }
        }

        #endregion
        #region PageItemEnd1

        private int? _PageItemEnd1;
        public int? PageItemEnd1
        {
            get
            {
                return _PageItemEnd1;
            }
            private set
            {
                if (_PageItemEnd1 == value)
                    return;
                _PageItemEnd1 = value;
                OnPropertyChanged(nameof(PageItemEnd1), true, false);
            }
        }

        #endregion
        #region PageItemEnd2

        private int? _PageItemEnd2;
        public int? PageItemEnd2
        {
            get
            {
                return _PageItemEnd2;
            }
            private set
            {
                if (_PageItemEnd2 == value)
                    return;
                _PageItemEnd2 = value;
                OnPropertyChanged(nameof(PageItemEnd2), true, false);
            }
        }

        #endregion
        #region PageItemEnd3

        private int? _PageItemEnd3;
        public int? PageItemEnd3
        {
            get
            {
                return _PageItemEnd3;
            }
            private set
            {
                if (_PageItemEnd3 == value)
                    return;
                _PageItemEnd3 = value;
                OnPropertyChanged(nameof(PageItemEnd3), true, false);
            }
        }

        #endregion
        #region PageItemEnd4

        private int? _PageItemEnd4;
        public int? PageItemEnd4
        {
            get
            {
                return _PageItemEnd4;
            }
            private set
            {
                if (_PageItemEnd4 == value)
                    return;
                _PageItemEnd4 = value;
                OnPropertyChanged(nameof(PageItemEnd4), true, false);
            }
        }

        #endregion

        #endregion
        #region Private Members

        private const int MaxPageItemCount = 4;

        #region MaxStart

        private int? MaxStart
        {
            get
            {
                return System.Linq.Enumerable.Max(new List<int?>()
                {
                    this.PageItemStart1,
                    this.PageItemStart2,
                    this.PageItemStart3,
                    this.PageItemStart4,
                });
            }
        }

        #endregion
        #region MaxStartAndPrevious

        private int? MaxStartAndPrevious
        {
            get
            {
                return System.Linq.Enumerable.Max(new List<int?>()
                {
                    this.PageItemStart1,
                    this.PageItemStart2,
                    this.PageItemStart3,
                    this.PageItemStart4,
                    this.PageItemPrevious1,
                    this.PageItemPrevious2,
                    this.PageItemPrevious3,
                    this.PageItemPrevious4,
                });
            }
        }

        #endregion
        #region MaxAllExceptStartAndEnd

        private int? MaxAllExceptStartAndEnd
        {
            get
            {
                if ((this.CurrentPage > this.MaxStart) && (this.CurrentPage < this.MinEnd))
                {
                    return System.Linq.Enumerable.Max(new List<int?>()
                    {
                        this.PageItemPrevious1,
                        this.PageItemPrevious2,
                        this.PageItemPrevious3,
                        this.PageItemPrevious4,
                        this.PageItemNext1,
                        this.PageItemNext2,
                        this.PageItemNext3,
                        this.PageItemNext4,
                        this.CurrentPage,
                    });
                }
                else
                {
                    return System.Linq.Enumerable.Max(new List<int?>()
                    {
                        this.PageItemPrevious1,
                        this.PageItemPrevious2,
                        this.PageItemPrevious3,
                        this.PageItemPrevious4,
                        this.PageItemNext1,
                        this.PageItemNext2,
                        this.PageItemNext3,
                        this.PageItemNext4,
                    });
                }
            }
        }

        #endregion
        #region MinEnd

        private int? MinEnd
        {
            get
            {
                return System.Linq.Enumerable.Min(new List<int?>()
                {
                    this.PageItemEnd1,
                    this.PageItemEnd2,
                    this.PageItemEnd3,
                    this.PageItemEnd4,
                });
            }
        }

        #endregion
        #region MinAllExceptStart

        private int? MinAllExceptStart
        {
            get
            {
                if (this.CurrentPage > this.MaxStart)
                {
                    return System.Linq.Enumerable.Min(new List<int?>()
                    {
                        this.PageItemPrevious1,
                        this.PageItemPrevious2,
                        this.PageItemPrevious3,
                        this.PageItemPrevious4,
                        this.PageItemNext1,
                        this.PageItemNext2,
                        this.PageItemNext3,
                        this.PageItemNext4,
                        this.PageItemEnd1,
                        this.PageItemEnd2,
                        this.PageItemEnd3,
                        this.PageItemEnd4,
                        this.CurrentPage,
                    });
                }
                else
                {
                    return System.Linq.Enumerable.Min(new List<int?>()
                    {
                        this.PageItemPrevious1,
                        this.PageItemPrevious2,
                        this.PageItemPrevious3,
                        this.PageItemPrevious4,
                        this.PageItemNext1,
                        this.PageItemNext2,
                        this.PageItemNext3,
                        this.PageItemNext4,
                        this.PageItemEnd1,
                        this.PageItemEnd2,
                        this.PageItemEnd3,
                        this.PageItemEnd4,
                    });
                }
            }
        }

        #endregion

        #region GetPageItemStart

        private int? GetPageItemStart(int pageItem)
        {
            if (pageItem < 1)
                return null;
            if (pageItem > this.PageItemStartCount)
                return null;
            return GetValidPage(pageItem, 1, this.PageCount);
        }

        #endregion
        #region GetPageItemPrevious

        private int? GetPageItemPrevious(int pageItem)
        {
            if (pageItem < 1)
                return null;
            if (pageItem > this.PageItemPreviousCount)
                return null;
            var max = System.Linq.Enumerable.Min(new List<int>()
            {
                this.CurrentPage - 1,
                MinEnd.ToInt32(0) - 1,
            });
            return GetValidPage(this.CurrentPage - 1 - this.PageItemPreviousCount + pageItem, MaxStart.ToInt32(0) + 1, max);
        }

        #endregion
        #region GetPageItemNext

        private int? GetPageItemNext(int pageItem)
        {
            if (pageItem < 1)
                return null;
            if (pageItem > this.PageItemNextCount)
                return null;
            var max = System.Linq.Enumerable.Min(new List<int>()
            {
                this.CurrentPage + this.PageItemNextCount,
                MinEnd.ToInt32(0) - 1,
            });
            return GetValidPage(this.CurrentPage + pageItem, MaxStartAndPrevious.ToInt32(0) + 1, max);
        }

        #endregion
        #region GetPageItemEnd

        private int? GetPageItemEnd(int pageItem)
        {
            if (pageItem < 1)
                return null;
            if (pageItem > this.PageItemEndCount)
                return null;
            return GetValidPage(this.PageCount - this.PageItemEndCount + pageItem, MaxStart.ToInt32(0) + 1, this.PageCount);
        }

        #endregion
        #region GetValidPage

        private static int? GetValidPage(int page, int minValue, int maxValue)
        {
            if (page > maxValue)
                return null;
            if (page < minValue)
                return null;
            return page;
        }

        #endregion
        #region GetValidPageUsingEndValues

        private int GetValidPageUsingEndValues(int page)
        {
            if (page >= this.PageCount)
                return this.PageCount;
            if (page < 1)
            {
                if (this.PageCount > 0)
                    return 1;
                return 0;
            }
            return page;
        }

        #endregion
        #region GetValidPageCount

        private static int GetValidPageCount(int pageCount)
        {
            if (pageCount >= MaxPageItemCount)
                return MaxPageItemCount;
            if (pageCount <= 0)
                return 0;
            return pageCount;
        }

        #endregion
        #region UpdateCurrentPage

        private void UpdateCurrentPage(int page, bool triggerPropertyChange)
        {
            page = GetValidPageUsingEndValues(page);
            if (_CurrentPage != page)
            {
                _CurrentPage = page;
                OnPropertyChanged(nameof(CurrentPage), true, true);
                triggerPropertyChange = true;
            }
            if (triggerPropertyChange)
            {
                OnPropertyChanged(nameof(CurrentStartPosition), true, false);
                OnPropertyChanged(nameof(CurrentEndPosition), true, false);
                OnPropertyChanged(nameof(CanPageBack), true, false);
                OnPropertyChanged(nameof(CanPageForward), true, false);
                OnPropertyChanged(nameof(Description), true, false);
                OnUpdatePageItemCount();
            }
        }

        #endregion
        #region OnUpdatePageItemCount

        private void OnUpdatePageItemCount()
        {
            this.PageItemStart1 = GetPageItemStart(1);
            this.PageItemStart2 = GetPageItemStart(2);
            this.PageItemStart3 = GetPageItemStart(3);
            this.PageItemStart4 = GetPageItemStart(4);

            this.PageItemEnd1 = GetPageItemEnd(1);
            this.PageItemEnd2 = GetPageItemEnd(2);
            this.PageItemEnd3 = GetPageItemEnd(3);
            this.PageItemEnd4 = GetPageItemEnd(4);

            this.PageItemPrevious1 = GetPageItemPrevious(1);
            this.PageItemPrevious2 = GetPageItemPrevious(2);
            this.PageItemPrevious3 = GetPageItemPrevious(3);
            this.PageItemPrevious4 = GetPageItemPrevious(4);

            this.PageItemNext1 = GetPageItemNext(1);
            this.PageItemNext2 = GetPageItemNext(2);
            this.PageItemNext3 = GetPageItemNext(3);
            this.PageItemNext4 = GetPageItemNext(4);

            OnPropertyChanged(nameof(HasPageItemCurrentPage), true, false);
            OnPropertyChanged(nameof(HasPageItemEndGap), true, false);
            OnPropertyChanged(nameof(HasPageItemStartGap), true, false);
        }

        #endregion

        #endregion
        #region Constructors

        public PageItem()
        {
        }
        public PageItem(long totalRowCount, int pageSize, int currentPage, int pageItemStartCount, int pageItemEndCount, int pageItemPreviousCount, int pageItemNextCount,
            string descriptionFormat)
        {
            _TotalRowCount = totalRowCount;
            _PageSize = pageSize;
            _CurrentPage = currentPage;
            _PageItemStartCount = pageItemStartCount;
            _PageItemEndCount = pageItemEndCount;
            _PageItemPreviousCount = pageItemPreviousCount;
            _PageItemNextCount = pageItemNextCount;
            _DescriptionFormat = descriptionFormat;
            OnUpdatePageItemCount();
        }

        #endregion
    }
}
